package com.github.common.extend.demo.config.filetask.download;

import com.github.common.extend.demo.config.filetask.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.text.MessageFormat;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;

/**
 * @author wangjj7
 * @date 2023/8/29
 * @description
 */
@Slf4j
public class FileTaskDownloaderImpl implements FileTaskDownloader {

    private final Supplier<String> idGenerator;

    private final FileTaskManager fileTaskManager;

    public FileTaskDownloaderImpl(Supplier<String> idGenerator, FileTaskManager fileTaskManager) {
        Objects.requireNonNull(idGenerator);
        Objects.requireNonNull(fileTaskManager);

        this.idGenerator = idGenerator;
        this.fileTaskManager = fileTaskManager;
    }

    @Override
    public void submitTaskThenDownload(FileTaskParam fileTaskParam, HttpServletResponse response){
        FileTask fileTask = fileTaskManager.submit(fileTaskParam);
        tryWriteResponse(fileTask ,response);
    }

    @Override
    public void asyncSubmitTaskThenDownload(FileTaskParam fileTaskParam, FileTaskCallback fileTaskCallback,
                                            HttpServletResponse response, long timeout, TimeUnit timeUnit)
    {
        String id = fileTaskParam.getId();
        if (StringUtils.isBlank(id)){
            id = String.valueOf(idGenerator.get());
            fileTaskParam.setId(id);
        }

        Future<FileTask> future = fileTaskManager.asyncSubmit(fileTaskParam, new FileTaskCallback() {
            @Override
            public void preExe(FileTaskParam taskSubmitDTO) {
            }

            @Override
            public void postExe(FileTask fileTask, Exception e) {
                tryWriteResponse(fileTask, response);
            }
        }.then(fileTaskCallback));

        try {
            //指定时间内完成，直接下载，否则通知去导出中心查看
            future.get(timeout, timeUnit);
        } catch (InterruptedException | ExecutionException e) {
            throw new IllegalStateException(MessageFormat.format(
                    "导出异常,请到导出中心查看任务详情，任务ID【{0}】,{1}", id ,
                    StringUtils.isBlank(e.getMessage()) ? "" : e.getMessage())
            );
        } catch (TimeoutException e) {
            throw new IllegalStateException(MessageFormat.format(
                    "导出超时，请到导出中心查看进度，任务ID【{0}】" ,id));
        }finally {
            FileTask taskDTO = fileTaskManager.get(id);
            if (taskDTO != null && !FileTaskStatus.UNDONE.getCode().equals(taskDTO.getStatus())){
                //如果还在执行文件上传任务，不能中断
                future.cancel(true);
            }
        }
    }

    //尝试将文件输出到response
    private void tryWriteResponse(FileTask fileTask , HttpServletResponse response)
    {
        Supplier<InputStream> fileSupplier = fileTask.getFileSupplier();
        if (fileSupplier != null) {
            String fileName = StringUtils.isBlank(fileTask.getFileName()) ? fileTask.getId() : fileTask.getFileName();
            String encodeFileName = fileName;
            try {
                encodeFileName = URLEncoder.encode(fileName,"utf-8");
            } catch (UnsupportedEncodingException e) {
                log.warn("文件名转码失败" ,e.getMessage());
            }

            response.setContentType(APPLICATION_OCTET_STREAM_VALUE);
            response.setHeader(CONTENT_DISPOSITION, "attachment;filename=" + encodeFileName);
            response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, CONTENT_DISPOSITION);

            try (InputStream inputStream = fileSupplier.get(); OutputStream outputStream = response.getOutputStream()){
                if (inputStream instanceof FileInputStream){
                    //零拷贝
                    FileChannel fileChannel = ((FileInputStream) inputStream).getChannel();
                    fileChannel.transferTo(0 ,fileChannel.size() ,Channels.newChannel(outputStream));
                }else {
                    IOUtils.copy(inputStream ,outputStream);
                }
            } catch (IOException e) {
                //通常是由于请求超时已响应
                log.warn("将文件输出到response异常，{}" ,e.getMessage());
            }
        }else {
            log.error("没有定义文件获取函数");
        }
    }

}
